﻿using System.Net;
using System.Security.Claims;
using Devonline.AspNetCore;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using IdentityModel;
using Microsoft.AspNetCore.SignalR.Protocol;
using System.Text.Json;
using Microsoft.AspNetCore.Http.HttpResults;
using System.Runtime.InteropServices;
using Microsoft.AspNetCore.Identity;

namespace Devonline.Identity;

/// <summary>
/// identity HttpClient service
/// </summary>
public class IdentityService
{
    private readonly ILogger<IdentityService> _logger;
    private readonly HttpSetting _httpSetting;
    private readonly HttpClient _httpClient;
    private readonly HttpContext _httpContext;
    private readonly IDistributedCache _cache;
    public IdentityService(
        ILogger<IdentityService> logger,
        HttpSetting httpSetting,
        HttpClient httpClient,
        IHttpContextAccessor httpContextAccessor,
        IDistributedCache cache
        )
    {
        ArgumentNullException.ThrowIfNull(httpContextAccessor.HttpContext);
        ArgumentNullException.ThrowIfNull(httpSetting.Identity);
        _logger = logger;
        _httpSetting = httpSetting;
        _httpClient = httpClient;
        _httpContext = httpContextAccessor.HttpContext;
        _cache = cache;
        _httpClient.BaseAddress = new Uri(_httpSetting.Identity.Authority);
        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Accept, ContentType.Json);
        _httpClient.DefaultRequestHeaders.Add(HeaderNames.UserAgent, _httpSetting.Identity.ClientId);
    }

    /// <summary>
    /// login to Identity Service
    /// </summary>
    /// <param name="model"></param>
    /// <returns></returns>
    public async Task LoginAsync(UserLoginModel model)
    {
        ArgumentNullException.ThrowIfNull(_httpSetting.UserInteraction);
        _logger.LogDebug("user {user} will login to Identity Service!", model.UserName);
        var httpContent = new StringContent(model.ToJsonString());
        httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(ContentType.Json);
        var response = await _httpClient.PostAsync(_httpSetting.UserInteraction.Login, httpContent);
        if (!response.IsSuccessStatusCode)
        {
            var content = await response.Content.ReadAsStringAsync();
            throw new BadHttpRequestException("登录失败!", (int)response.StatusCode, new Exception("登录失败, 详情: " + content));
        }

        await SignInAsync();
    }

    /// <summary>
    /// 登录
    /// </summary>
    /// <returns></returns>
    public async Task<UserInfo> TokenLoginAsync(UserLoginModel model)
    {
        ArgumentNullException.ThrowIfNull(model.UserName);
        _logger.LogDebug("user {user} will login from Identity Service!", model.UserName);
        var token = await GetIdentityTokenAsync(model);
        ArgumentNullException.ThrowIfNull(token);
        _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(AppSettings.DEFAULT_AUTHENTICATION_SCHEME, token.AccessToken);
        var identityUserInfo = await GetIdentityUserInfoAsync(model.UserName);
        ArgumentNullException.ThrowIfNull(identityUserInfo);
        _logger.LogInformation("user {user} login success and get the token and userinfo from Identity Service!", model.UserName);
        return new UserInfo { Token = token, User = identityUserInfo.CopyTo<UserViewModel>() };
    }

    /// <summary>
    /// 微信app,小程序登录
    /// </summary>
    /// <param name="loginType"></param>
    /// <param name="code"></param>
    /// <returns></returns>
    public async Task WeixinLoginAsync(string loginType, string code)
    {
        _logger.LogDebug("user will login from Identity Service for Weixin!");
        ArgumentNullException.ThrowIfNull(_httpSetting.UserInteraction);
        //获取微信信息
        using var request = GetHttpRequest(System.Net.Http.HttpMethod.Get, _httpSetting.UserInteraction.WeixinLogin + AppSettings.CHAR_QUESTION + nameof(loginType) + AppSettings.CHAR_EQUAL + loginType + AppSettings.CHAR_ADD + nameof(code) + AppSettings.CHAR_EQUAL + code);
        var response = await _httpClient.SendAsync(request);
        var content = await response.Content.ReadAsStringAsync();
        if (!response.IsSuccessStatusCode)
        {
            throw new BadHttpRequestException("获取微信信息失败!", (int)response.StatusCode, new Exception("获取微信信息失败, 详情: " + content));
        }

        //授权端登录
        response = await _httpClient.GetAsync(_httpSetting.UserInteraction.WexinAuthentication);
        content = await response.Content.ReadAsStringAsync();

        if (!response.IsSuccessStatusCode)
        {
            throw new BadHttpRequestException("微信登录失败!", (int)response.StatusCode, new Exception("微信登录失败, 详情: " + content));
        }

        #region 暂不使用该方式
        //if (!response.Headers.TryGetValues("Set-Cookie", out IEnumerable<string>? cookies) || !cookies.Any())
        //{
        //    throw new BadHttpRequestException("微信登录失败, 未能正确返回 Cookie !", (int)response.StatusCode, new Exception("微信登录失败, 未能正确返回 Cookie!, 详情: " + content));
        //}
        //foreach (var cookie in cookies)
        //{
        //    var keyIndex = cookie.IndexOf('=');
        //    _httpContext.Response.Cookies.Append(cookie[..keyIndex], cookie[(keyIndex + 1)..]);
        //}
        #endregion

        await SignInAsync();
    }

    /// <summary>
    /// 退出登录
    /// </summary>
    /// <returns></returns>
    public async Task LogoutAsync()
    {
        var userName = _httpContext.GetUserName();
        _logger.LogDebug("user {user} will logout from Identity Service!", userName);
        ArgumentNullException.ThrowIfNull(_httpSetting.UserInteraction);
        var response = await _httpClient.GetAsync(_httpSetting.UserInteraction.Logout);
        var result = response.IsSuccessStatusCode ? "success" : (await response.Content.ReadAsStringAsync());
        _httpClient.DefaultRequestHeaders.Remove("Cookie");
        _httpClient.DefaultRequestHeaders.Remove("Authorization");
        _logger.LogDebug("user {user} logout from Identity Service: " + result, userName);
    }

    /// <summary>
    /// 真正的登录,设置cookie
    /// </summary>
    /// <returns></returns>
    public async Task SignInAsync()
    {
        var userInfo = await GetUserInfoAsync();
        if (userInfo is not null && userInfo.User is not null)
        {
            var user = userInfo.User;
            ArgumentNullException.ThrowIfNull(user.Name);
            ArgumentNullException.ThrowIfNull(user.UserName);
            var claims = new List<Claim>{
            new(JwtClaimTypes.Name, user.Name),
            new(AppSettings. CLAIM_TYPE_USER_ID, user.Id),
            new(AppSettings.CLAIM_TYPE_USER_NAME, user.UserName)};

            string scheme = CookieAuthenticationDefaults.AuthenticationScheme;
            var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, scheme));
            await _httpContext.SignInAsync(scheme, claimsPrincipal, new AuthenticationProperties { IsPersistent = true, Items = { { nameof(scheme), scheme } } });
            _logger.LogInformation($"user {userInfo.User.UserName} login from Identity Service success! ");
        };
    }

    /// <summary>
    /// 获取用户信息
    /// </summary>
    /// <returns></returns>
    public async Task<UserInfo?> GetUserInfoAsync()
    {
        //var userInfo = await GetUserInfoFromCacheAsync();
        //if (userInfo is not null && userInfo.User is not null)
        //{
        //    return userInfo;
        //}

        //var userName = _httpContext.GetUserName();
        //_logger.LogDebug($"user {userName} will get user info from Identity Service for current system");
        ArgumentNullException.ThrowIfNull(_httpSetting.UserInteraction);

        string system = _httpContext.Request.GetRequestOrigin();
        using var request = GetHttpRequest(System.Net.Http.HttpMethod.Get, _httpSetting.UserInteraction.UserInfo);
        var response = await _httpClient.SendAsync(request);
        var result = await response.Content.ReadAsStringAsync();
        if (!response.IsSuccessStatusCode)
        {
            throw new BadHttpRequestException("获取用户信息失败!", (int)response.StatusCode, new Exception("获取用户信息失败, 详情: " + result));
        }

        var userInfo = result.ToJsonObject<UserInfo>();
        if (userInfo is not null && userInfo.User is not null)
        {
            _logger.LogInformation("user {user} get userinfo from Identity Service success", userInfo.User.UserName);
            return userInfo;
        }

        return default;
    }
    /// <summary>
    /// 给当前用户自动添加角色, 此处只能设置内部角色类型更低的类型
    /// </summary>
    /// <param name="roleName">角色名, 角色不能是内部角色以上类型的角色</param>
    /// <returns></returns>
    public async Task<bool> AutoRoleAsync(string roleName)
    {
        var userName = _httpContext.GetUserName();
        _logger.LogDebug("user {user} will auto set role {role} from Identity Service!", userName, roleName);
        ArgumentNullException.ThrowIfNull(_httpSetting.UserInteraction);

        using var request = GetHttpRequest(System.Net.Http.HttpMethod.Post, _httpSetting.UserInteraction.AutoRole + AppSettings.CHAR_SLASH + roleName);
        var response = await _httpClient.SendAsync(request);
        var result = response.IsSuccessStatusCode ? "success" : (await response.Content.ReadAsStringAsync());
        _logger.LogDebug("user {user} auto set role {role} from Identity Service: " + result, userName, roleName);

        return response.IsSuccessStatusCode;
    }

    /// <summary>
    /// 获取 access token
    /// </summary>
    /// <param name="model"></param>
    /// <returns></returns>
    private async Task<IdentityToken?> GetIdentityTokenAsync(UserLoginModel model)
    {
        ArgumentNullException.ThrowIfNull(_httpSetting.Identity);
        ArgumentNullException.ThrowIfNull(_httpSetting.Identity.ClientId);
        ArgumentNullException.ThrowIfNull(_httpSetting.Identity.ClientSecret);
        ArgumentNullException.ThrowIfNull(_httpSetting.Identity.GrantType);
        _logger.LogDebug("user {user} will get token from Identity Service!", model.UserName);
        var dic = new Dictionary<string, string>();
        var column = _httpSetting.Identity.GetColumnName(nameof(_httpSetting.Identity.ClientId));
        if (column is not null)
        {
            dic.Add(column, _httpSetting.Identity.ClientId);
        }

        column = _httpSetting.Identity.GetColumnName(nameof(_httpSetting.Identity.ClientSecret));
        if (column is not null)
        {
            dic.Add(column, _httpSetting.Identity.ClientSecret);
        }

        column = _httpSetting.Identity.GetColumnName(nameof(_httpSetting.Identity.GrantType));
        if (column is not null)
        {
            dic.Add(column, _httpSetting.Identity.GrantType);
        }

        column = model.GetColumnName(nameof(model.UserName));
        if (column is not null && model.UserName is not null)
        {
            dic.Add(column, model.UserName);
        }

        column = model.GetColumnName(nameof(model.Password));
        if (column is not null && model.Password is not null)
        {
            dic.Add(column, model.Password);
        }

        var httpContent = new FormUrlEncodedContent(dic);
        httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(ContentType.FormUrlencoded);
        var response = await _httpClient.PostAsync(_httpSetting.Identity.Token, httpContent);
        var result = await response.Content.ReadAsStringAsync();
        if (response.IsSuccessStatusCode)
        {
            var token = result.ToJsonObject<IdentityToken>();
            if (token is not null)
            {
                _logger.LogInformation("user {user} get access token success: " + result, model.UserName);
                return token;
            }
        }

        _logger.LogDebug("user {user} get token from Identity Service failed: " + result, model.UserName);
        return default;
    }
    /// <summary>
    /// get identity userinfo from identity service
    /// </summary>
    /// <param name="userName"></param>
    /// <returns></returns>
    private async Task<IdentityUserInfo?> GetIdentityUserInfoAsync(string userName)
    {
        ArgumentNullException.ThrowIfNull(_httpSetting.Identity);
        _logger.LogDebug("user {user} will get userinfo from Identity Service!", userName);
        var response = await _httpClient.GetAsync(_httpSetting.Identity.UserInfo);
        var result = await response.Content.ReadAsStringAsync();
        if (response.IsSuccessStatusCode)
        {
            var userInfo = result.ToJsonObject<IdentityUserInfo>();
            if (userInfo is not null)
            {
                userInfo.PhoneNumber = userInfo.PhoneNumber.Desensitize();
                _logger.LogInformation("user {user} get userinfo from Identity Service success: " + result, userName);
                return userInfo;
            }
        }

        _logger.LogDebug("user {user} get userinfo from Identity Service failed: " + result, userName);
        return default;
    }

    /// <summary>
    /// 从缓存中获取用户信息
    /// </summary>
    /// <returns></returns>
    private async Task<UserInfo?> GetUserInfoFromCacheAsync()
    {
        var userName = _httpContext.GetUserName();
        var userContext = await _cache.GetValueAsync<UserContext>(AppSettings.CACHE_USER + userName);
        return userContext?.GetUserInfo(_httpContext);
    }
    /// <summary>
    /// 根据上下文创建新的 Http 请求
    /// </summary>
    /// <param name="httpMethod">请求类型</param>
    /// <param name="url">请求地址</param>
    private HttpRequestMessage GetHttpRequest(System.Net.Http.HttpMethod httpMethod, string url)
    {
        var userName = _httpContext.GetUserName();
        var request = new HttpRequestMessage(httpMethod, url);
        if (_httpContext.Request.Cookies.Any())
        {
            var cookies = _httpContext.Request.Headers.Cookie.ToString();
            _logger.LogDebug("user {user} will get user info from Identity Service take the request Cookies: " + cookies, userName);
            request.Headers.Add("Cookie", cookies);
        }

        if (!string.IsNullOrWhiteSpace(_httpContext.Request.Headers.Authorization) && System.Net.Http.Headers.AuthenticationHeaderValue.TryParse(_httpContext.Request.Headers.Authorization, out var authentication))
        {
            _logger.LogDebug("user {user} will get user info from Identity Service take the request Authorization", userName);
            request.Headers.Authorization = authentication;
        }

        return request;
    }
}